Genesis 3D

 

per collaborazioni, commenti, critiche, e altro contattateci alla e-mail: clubinfo@libero.it risponderemo al più presto!

Corso sull'engine 3D di Genesis

Tredicesima lezione

di Luca Sabatucci

Per commentare, dare suggerimenti o consigli (molto apprezzati) e per segnalare eventuali errori o
disattenzioni (sempre possibili), puoi inviare una email all'autore (clicca sul nome sopra). Grazie per la collaborazione!


Tecniche avanzate di programmazione

In questo tutorial vedremo:

1) Mappe e modelli procedurali

2) Modelli basati sulla grammatica

3) Sistemi particellari

4) Sistemi basati sulla fisica

5) Corona e Dynamic Light

Introduzione

Molti fenomeni naturali non sono rappresentati in modo efficiente mediante le tecniche di modellazione standard. La nebbia per esempio è composta da una moltitudine di minuscole particelle d'acqua; realizzare l'effetto nebbia tenendo conto di tutte queste molecole, ogniuna delle quali deve essere posizionata è proibitivo. Inoltre nessuno ci assicura che al momento del rendering l'effetto ottenuto è esattamente quello desiderato.

Per risolvere il problema dobbiamo concentrarci su quella che è la nostra percezione della nebbia, ovvero come la nebbia altera il nostro sistema di percezione della luce nell'ambiente.

Il modello che si ottiene è quindi più efficiente, e adatto anche ad una renderizzazione in tempo reale (si noti come molti giochi usino l'effetto nebbia in modo intensivo).

I fenomeni naturali non sono gli unici che richiedono tecniche avanzate: si pensi al ponte di Brooklyn nei dettagli. Esso è realizzato da bulloni e dadi non sempre posizionati nelle stesse posizioni. Il problema di modellare un oggetto così complesso può essere superato andando oltre la modellazione geometrica.

Modelli procedurali

I modelli procedurali rappresentano degli oggetti in grado di interagire con eventi esterni e di modificare la propria struttura (sia essa la geometria, oppure altri parametri come l'origine del sistema di coordinate).

Ad esempio una sfera che genera una rappresentazione poligonale differente in base ad un parametro di ingresso è un modello procedurale.

Il grande vantaggio dei modelli procedurali è che permettono di risparmiare spazio. Infatti è possibile sostituire a "collezioni di oggetti" la loro descrizione parametrica. Si pensi alla descrizione procedurale di un ponte. Esso è composto da sottomodelli come strade, piloni e parapetti e può essere specificato fornendo la descrizione di questi elementi. Ogni "pezzo" è definito da una serie di parametri e da una procedura che specifica come posizionarlo.

Un importante aspetto dei modelli procedurali è la loro abilità di interagire con l'ambiente. Tale capacità viene usata per controllare la forma dell'oggetto definita nella procedura.

 

Un particolare uso dei modelli procedurali sono le mappe procedurali. Si tratta di una texture che in base ad alcuni parametri può essere variata (si può modificare il colore, il motivo e l'aspetto generale). 3D studio MAX offre tale caratteristica, suddividendola in mappe 2D e mappe 3D. La mappa 2D non è altro che una texture procedurale, che può essere applicata poi ad un oggetto. MAX offre già alcune mappe pronte, quali scacchiera, gradiente e vortice. Le mappe 3D invece rappresentano un modo per definire le caratteristiche del materiale dell'oggetto senza passare attraverso la bitmap (come nel caso 2D). Il motivo generato è direttamente nello spazio 3D. Alcune mappe 3D sono legno e marmo.

 

Esempi di mappe 2D (gradiente e vortice)

 

 

Esempi di mappe 3D

 

 

Genesis supporta pure le mappe procedurali (ma solo quelle che MAX definisce 2D, ovvero bitmap). Tuttavia si tratta di una caratteristica estremamente poco documentata e che risulta difficile da sperimentare. Infatti non è possibile al momento esportare le mappe di MAX per l'uso in genesis. La più evidente mappa procedurale (gestita automaticamente nell'engine di genesis) è lo specchio; una texture impostata come specchio cambia a seconda dell'ambiente circostante.

Per definire delle mappe procedurali in genesis si deve creare un file .prc che deve essere associato al file .bsp della mappa.

Tale file deve contenere una descrizione della mappa:

 

wbm: FireProc1 Smoke (Smoke_PalFire, 128, 6.0, 64.0, 25.0, 4.0, 5.0, 1.0);

 

dove FireProc1 è la texture (che deve esistere nel file .txl) alla quale si vuole associare l'effetto; smoke è il nome della procedura che contiene l'effetto (nel caso specifico il fumo); i restanti sono i parametri standard della procedura. Naturalmente in fase di esecuzione è possibile variare tali parametri e quindi l'effetto complessivo del fumo.

Modelli basati sulla grammatica

Si tratta di veri e propri linguaggi usati per fornire la descrizione di oggetti la cui struttura è ordinata come quella di una pianta. Tali linguaggi sono descritti da una grammatica che consiste della collezione di produzioni. Ad esempio:

 

1. A -> AA

2. B -> A[B]AA[B]

 

Partendo dall'assioma A si generano A, AA, AAAA e così via. partendo dall'assioma B si generano B, A[B]AA[B], AA[A[B]AA[B]]AA[A[B]AA[B]], e così via.

 

A tale struttura possiamo associare un grafo o un albero arrivando a generare così in maniera automatica strutture grandi quanto si vuole. Il grafo può a sua volta essere rappresentato come una pianta, dove le A rappresentano le parti di tronco e le B le foglie. Con le parentesi quindi si specificano i rami della pianta.

Sistemi particellari

Acqua, fuoco, neve, fumo, pioggia sono tutti esempi di fenomeni che vengono resi mediante sistemi particellari. Un sistema particellare è definito come una collezione di oggetti elementari (particelle) che evolve nel tempo. L'evoluzione è determinata applicando certe regole probabilistiche. Le particelle posseggono una propria evoluzione che parte dalla nascita, dalla crescita e dallo spostamento e finisce con la morte. L'essenza del sistema particellare è che la posizione e l'evoluzione delle singole particelle è controllata in modo automatico e ogni particella influisce direttamente l'immagine.

Il rendering dei sistemi particellari è un argomento notevole. Effettuare il ray tracing di un sistema di particelle, ognuna con il proprio bounding box diventa impossibile quando le particelle sono numerose. Una soluzione fornita da Reeves è quella di considerare ogni particella come una singola sorgente di luce e di calcolare l'apporto di ogni singola sorgente sull'immagine fiale. Il valore di ogni pixel viene determinato dall'accumulo delle componenti di ogni particella.

 

3D studio MAX ha un buon supporto dei sistemi particellari. In MAX le singole particelle rispondono a forze comuni e sono intrinsecamente animati. Le particelle vengono emesse da una sorgente e si spostano lungo un percorso regolare. E' possibile controllare in modo abbastanza sofisticato il comportamento delle particelle mediante l'uso di vari modificatori space warp.

Inoltre in fase di rendering è possibile associare ad ogni particella delle istanze di oggetti. In questo modo ad esempio è possibile modellare un branco di pesci semplicemente associando ad ogni particella la geometria di un pesce.

 

Esempio di neve:

Sistemi basati sulla fisica

Il comportamento e la forma di molti oggetti è determinata dalle leggi fisiche che regolano il nostro mondo e dalle caratteristiche intrinseche dell'oggetto. Esempi di leggi fisica sono la gravità, la conservazione della quantità di moto, la trasformazione dell'energia cinetica in potenziale, mentre esempi di caratteristiche degli oggetti sono il peso, il coefficiente elastico, ecc...

In questo tipo di modellazione, non è l'utente a determinare le curve e i percorsi dell'animazione. L'utente stabilisce quali sono gli oggetti in gioco e le loro proprietà fisiche e poi è l'applicazione che mediante l'uso di leggi matematiche calcola il comportamento degli oggetti, tenendo conto anche delle interazioni tra di essi.

 

3D studio MAX possiede questi sistemi, che chiama "simulazioni dinamiche". Si tratta di un metodo di generazione automatica delle curve di animazione che poi possono modificate. Tali simulazioni hanno una grande consumo di memoria e tempo macchina, che naturalmente aumentano ancora con l'aumentare del numero di oggetti e con la precisione richiesta. E' inoltre richiesta, da parte dell'utente, una buona conoscenza della fisica.

 

Genesis offre la possibilità di creare dei sistemi fisici, ma anche questa è una caratteristica poco documentata. Per creare un sistema basato sulla fisica si deve aggiungere un oggetto di tipo PhysicalSytem al world. Quindi si devono creare degli oggetti "fisici" la cui struttura è data di seguito:

 

gePhysicsObject_Create(const geVec3d *StartLocation,

                                               float mass,

                                               geBoolean IsAffectedByGravity,

                                               geBoolean RespondsToForces,

                                               float linearDamping,

                                               float angularDamping,

                                               const geVec3d *          Mins,

                                               const geVec3d *          Maxs,

                                               float physicsScale);

 

Come si vede si può assegnare una massa, se è affetto da gravità e dalle forze fisiche che è possibile definire, se ha uno smorzamento nel movimento e nella rotazione e il bounding box.

Il parametro physicsScale serve per scalare i valori di tutte le forze che agiscono sull'oggetto.

Un PhysicsObject è soggetto ai colcoli generati da PhysicsSystem. L'oggetto PhysicsJoint serve a connettere tra di loro due PhysicsObjects per generare effetti come l'intreccio di una catena.

 

gePhysicsJoint_Create(gePhysicsJoint_Kind Kind,

                                               const geVec3d *Location,

                                               float assemblyRate,

                                               gePhysicsObject *PS1,

                                               gePhysicsObject *PS2,

                                               float physicsScale);

 

Per quanto riguarda il tipo si ha:

typedef enum { JT_WORLD = 0, JT_SPHERICAL, JT_PTTOPATH, JT_PTTOSURFACE} gePhysicsJoint_Kind;

ma del significato di questo e degli altri parametri non viene data alcuna spiegazione.

 

Poi mediante funzioni tipo

 

gePhysicsSystem_Iterate() viene aggiornato il sistema fisico (ovvero vengono eseguiti i calcoli).

 

gePhysicsObject_GetXForm() restituisce la matrice di trasformazione per l'oggetto da applicare alla geometria per ottenere la posizione e l'orientamento corrente dell'oggetto.

Corona e Dynamic Light

Finiamo questo tutorial con due effetti speciali offerti dall'engine di Genesis e per nulla difficili da utilizzare. Si tratta dell'effetto Corona e delle luci dinamiche.

 

Effetto Corona

Spiegare l'effetto corona viene immensamente meglio se si correda la spiegazione con una semplice immagine illustrativa:

 

 

Questo effetto di luce, tipico dei cartoni animati è molto facile da realizzare.

Ecco il codice per l'inizializzazione:

 

void Corona_Init(geEngine *TheEngine, geWorld *CWorld, geVFile *MainFS) {

 

      CoronaBitmap = geBitmapUtil_CreateFromFileAndAlphaNames(MainFS, "Bmp\\Corona.Bmp", "Bmp\\Corona_a.Bmp");

 

      geWorld_AddBitmap(World, CoronaBitmap);

 

}

 

Sostanzialmente viene caricata una immagine bitmap chiamata corona insieme ad un'altra immagine che funge da livello Alpha per la prima.

La funzione per il rendering

 

Corona_Frame(geWorld *World, const geXForm3d *XForm, geFloat DeltaTime) {

      geEntity_EntitySet      *Set;

      geEntity *              Entity;

      GE_Collision            Collision;

 

      Set = geWorld_GetEntitySet(World, "Corona");

     

      if (Set == NULL)

            return GE_TRUE;

 

      Entity = geEntity_EntitySetGetNextEntity(Set, NULL);

 

      while (Entity)

      {

            Corona *    C;

            geVec3d     Pos;

            geFloat     DistanceToCorona;

            geVec3d     Delta;

            geFloat     Radius;

            geBoolean   Visible;

            geBoolean   Fading;

            int32       Leaf;

 

            C = geEntity_GetUserData(Entity);

           

[codice per determinare se il punto è visibile]

     

            if    (Visible || Fading) {

                  GE_LVertex  Vert;

                 

                  Vert.X = Pos.X;

                  Vert.Y = Pos.Y;

                  Vert.Z = Pos.Z;

                  Vert.r = C->Color.r;

                  Vert.g = C->Color.g;

                  Vert.b = C->Color.b;

                  Vert.a = 255.0f;

                  Vert.u = Vert.v = 0.0f;

 

                  if    (Visible) {

                        if    (DistanceToCorona >= C->MaxRadiusDistance)

                             Radius = (geFloat)C->MaxRadius;

                        else if     (DistanceToCorona <= C->MinRadiusDistance)

                             Radius = (geFloat)C->MinRadius;

                        else {

                             geFloat     Slope;

 

                             Slope = (geFloat)(C->MaxRadius - C->MinRadius) / (geFloat)(C->MaxRadiusDistance - C->MinRadiusDistance);

                             Radius = (geFloat)C->MinRadius + Slope * (geFloat)(DistanceToCorona - C->MinRadiusDistance);

                        }

                        C->LastVisibleRadius = Radius;

                  } else

                        Radius = (1.0f - (C->LastTime - C->LastVisibleTime) / C->FadeTime) * C->LastVisibleRadius;

 

                  geWorld_AddPolyOnce(World,

                                               &Vert,

                                               1,

                                               CoronaBitmap,

                                               GE_TEXTURED_POINT,

                                               GE_RENDER_DO_NOT_OCCLUDE_OTHERS | GE_RENDER_DO_NOT_OCCLUDE_SELF,

                                               Radius * EffectScale);

 

                  C->LastTime += DeltaTime;

            }

 

            Entity = geEntity_EntitySetGetNextEntity(Set, Entity);

      }

 

}

 

Non fa altro che posizionare un poligono con la texture Corona nel punto desiderato in modo che sia sempre rivolta verso la camera (e quindi non si noti il fatto che si tratta di una immagine 2D).

 

Effetto Dynamic Light

Effetto sofisticato che serve a riprodurre una luce di intensità non costante. La variazione è totalmente programmabile dall'utente. E' ottimale per creare effetti come torce, luci difettose che tardano ad accendersi e contribuiscono a creare atmosfera alla scena.

 

Ecco il codice:

 

geBoolean DynLight_Frame(geWorld *World, const geXForm3d *XForm, geFloat DeltaTime)

{

      geEntity_EntitySet      *Set;

      geEntity *              Entity;

 

      Set = geWorld_GetEntitySet(World, "DynamicLight");

 

      Entity = geEntity_EntitySetGetNextEntity(Set, NULL);

      while (Entity) {

            DynamicLight      *Light;

            geFloat           Radius;

            geFloat           Percentage;

            int               Index;

            geVec3d           Pos;

 

            Light = geEntity_GetUserData(Entity);

            assert(Light->DynLight);

 

            if    (Light->Model) {

                  geXForm3d   XForm;

 

                  geWorld_GetModelXForm(World, Light->Model, &XForm);

 

                  Pos = Light->origin;

                  if    (Light->AllowRotation) {

                        geVec3d     Center;

 

                        geWorld_GetModelRotationalCenter(World, Light->Model, &Center);

                        geVec3d_Subtract(&Pos, &Center, &Pos);

                        geXForm3d_Transform(&XForm, &Pos, &Pos);

                        geVec3d_Add(&Pos, &Center, &Pos);

                  } else

                        geVec3d_Add(&Pos, &XForm.Translation, &Pos);

            } else

                  Pos = Light->origin;

 

            Percentage = Light->LastTime / Light->RadiusSpeed;

 

            Index = (int)(Percentage * Light->NumFunctionValues);

 

            if    (Light->InterpolateValues && Index < Light->NumFunctionValues - 1) {

                  geFloat     Remainder;

                  geFloat     InterpolationPercentage;

                  int         DeltaValue;

                  geFloat     Value;

 

                  Remainder = (geFloat)fmod(Light->LastTime, Light->IntervalWidth);

                  InterpolationPercentage = Remainder / Light->IntervalWidth;

 

                  DeltaValue = Light->RadiusFunction[Index + 1] - Light->RadiusFunction[Index];

                  Value = Light->RadiusFunction[Index] + DeltaValue * InterpolationPercentage;

                  Percentage = ((geFloat)(Value - 'a')) / ((geFloat)('z' - 'a'));

            } else

                  Percentage = ((geFloat)(Light->RadiusFunction[Index] - 'a')) / ((geFloat)('z' - 'a'));

 

            Radius = Percentage * (Light->MaxRadius - Light->MinRadius) + Light->MinRadius;

 

            geWorld_SetLightAttributes(World,

                                   Light->DynLight,

                                   &Pos,

                                   &Light->Color,

                                   Radius,

                                   GE_TRUE);

 

            Light->LastTime = (geFloat)fmod(Light->LastTime + DeltaTime, Light->RadiusSpeed);

 

            Entity = geEntity_EntitySetGetNextEntity(Set, Entity);

      }

 

      return GE_TRUE;

}

 

In pratica consiste nel variare gli attributi di una sorgente di luce mediante il comando geWorld_SetLightAttributes.

 

 

Questo articolo è stato scaricato dal Club di informatica
Pagina curata da:
Luca Sabatucci